package com.meego.common.crypto;

import com.meego.common.log.GA;
import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class SignedOutputStream extends OutputStream {

    private Cipher cipher;
    private MessageDigest digest;
    private byte[] decrypt;
    private int pos;
    private OutputStream out;

    public SignedOutputStream(OutputStream aTarget, char[] key)
            throws IOException {
        this.out = aTarget;
        try {
            this.cipher = Cipher.getInstance("DESede/CBC/NoPadding");

            byte[] bits = new byte[24];

            for (int i = 0; i < 24; i++) {
                bits[i] = (byte) key[(i % key.length)];
            }

            this.cipher.init(Cipher.ENCRYPT_MODE, SecretKeyFactory.getInstance("DESede").generateSecret(new DESedeKeySpec(bits)), new IvParameterSpec(new byte[8]));

            this.digest = MessageDigest.getInstance("MD5");
        } catch (Exception ex) {
            GA.crypto.error("Failure in creating a new instance of SignedOutputStream using the given key", ex);
            throw new IOException(ex.toString());
        }

        int size = this.cipher.getBlockSize();
        if (size == 0) {
            this.decrypt = new byte[8];
        } else {
            this.decrypt = new byte[size];
        }
    }

    @Override
    public void close() throws IOException {
        writeCipher(255);
        writeCipher(0);

        byte[] d = this.digest.digest();
        for (int i = 0; i < d.length; i++) {
            int b = d[i];
            if (b < 0) {
                b += 256;
            }

            if (b == 255) {
                writeCipher(255);
            }
            writeCipher(b);
        }

        writeCipher(255);
        writeCipher(0);

        while (this.pos > 0) {
            writeCipher(0);
        }

        this.out.flush();
        this.out.close();
    }

    @Override
    public void write(int aData) throws IOException {
        aData %= 256;
        if (aData == 255) {
            writeCipher(255);
        }
        writeCipher(aData);
        this.digest.update((byte) aData);
    }

    private void safeWrite(byte[] bits) throws IOException {
        if (bits == null) {
            return;
        }
        this.out.write(bits);
    }

    private void writeCipher(int abyte) throws IOException {
        this.decrypt[(this.pos++)] = (byte) abyte;
        if (this.pos == this.decrypt.length) {
            this.pos = 0;
            try {
                safeWrite(this.cipher.update(this.decrypt));
            } catch (Exception ex) {
                GA.crypto.error("Failed to write cipher", ex);
                throw new IOException(ex.toString());
            }
        }
    }
}
